const clouddb = require('@hw-agconnect/database-server/dist/index.js');
const agconnect = require('@hw-agconnect/common-server');
const path = require('path');
const CryptoJS = require('crypto-js')

/*
    以下代码不需要同学们理解
    * 可以直接复制我的代码
*/
//TODO 将AGC官网下载的配置文件放入resources文件夹下并将文件名替换为真实文件名
const credentialPath = "resources/agc-apiclient-1405248561209182976-7361036096769759511.json";
// 修改为在管理台创建的存储区名称
let zoneName = "db"
// 修改为需要操作的对象
let objectName = 'payRecords';

let logger

let mCloudDBZone

class CloudDBZoneWrapper {

    // AGC & 数据库初始化
    constructor(log) {
        logger = log;

        let agcClient;

        try {
            agcClient = agconnect.AGCClient.getInstance();
        } catch (error) {
            agconnect.AGCClient.initialize(agconnect.CredentialParser.toCredential(path.join(__dirname, credentialPath)));
            agcClient = agconnect.AGCClient.getInstance();
        }

        clouddb.AGConnectCloudDB.initialize(agcClient);

        const cloudDBZoneConfig = new clouddb.CloudDBZoneConfig(zoneName);

        const agconnectCloudDB = clouddb.AGConnectCloudDB.getInstance(agcClient);
        mCloudDBZone = agconnectCloudDB.openCloudDBZone(cloudDBZoneConfig);
    }

    /**
     * 转账
     * 这块属于纯后端逻辑，牵扯到后端的知识点可能比较多
     * 可以选择倾听，不需要较真
     * @param payload
     * @returns
     */
    async add(payload) {
        if (!mCloudDBZone) {
            logger.info("CloudDBClient is null, try re-initialize it");
            console.log("CloudDBClient is null, try re-initialize it")
            return;
        }
        /**
         * 当前业务模型，一次性至少要操作两条数据
         * 转账人的数据和收账人数据
         * 那么我们必须要保证当前收账人成功收账，转账人成功转账后，我们在进行实际的数据操作
         * 哪怕有一方失败，我们要保证两条数据都可以回退到之前
         * 所以说这种业务，需要使用事务
         */
        await mCloudDBZone.runTransaction({
            apply: (transaction) => this.doAdd(transaction, payload)
        })
    }

    async doAdd (transaction, payload) {
        try {
            const cardsOfPayee = await transaction.executeQuery(
                clouddb.CloudDBZoneQuery.where('bankCard')
                    .equalTo('uid', payload.payee)
            )
            // 如果被付款人没有银行卡则中断事务
            if (!cardsOfPayee[0]) return false

            const cardsOfPayer = await transaction.executeQuery(
                clouddb.CloudDBZoneQuery.where('bankCard')
                    .equalTo('uid', payload.payer)
            )
            // 如果付款人没有银行卡 或者 超出付款人余额 则中断事务
            if (!cardsOfPayer[0] || cardsOfPayer[0].getFieldValue('amount') < payload.amount) return false

            const cardOfPayer = cardsOfPayer[0]
            const cardOfPayee = cardsOfPayee[0]

            // 注意卡片对象为CloudDBZoneGenericObject，因此需要通过addFieldValue进行值的更新
            cardOfPayer.addFieldValue('amount', cardOfPayer.getFieldValue('amount') - payload.amount)
            cardOfPayee.addFieldValue('amount', cardOfPayee.getFieldValue('amount') + payload.amount)
            // 更新付款双方的余额
            transaction.executeUpsert([cardOfPayer])
            transaction.executeUpsert([cardOfPayee])

            // 创建支付记录
            const payRecord = clouddb.CloudDBZoneGenericObject.build('payRecords')
            const createdAt = Date.now();

            payRecord.addFieldValue('payer', payload.payer)
            payRecord.addFieldValue('payee', payload.payee)
            payRecord.addFieldValue('amount', payload.amount)
            payRecord.addFieldValue('createdAt', createdAt)

            // 通过哈希算法 生成支付记录ID
            const payid = CryptoJS.SHA256(`${payload.payer}-${payload.payee}-${payload.amount}-${createdAt}`).toString()
            payRecord.addFieldValue('payid', payid)
            // // 新增支付记录
            transaction.executeUpsert([payRecord])
            return true
        } catch (e) {
            console.log('add =>', e)
        }
    }

    /**
     * 查询转账记录
     * @param payload
     *
     * 有分页功能
     * page pageSize
     *
     * 查询的时候查看的事当前登录人的数据
     * 当前登录人的uid
     *
     *
     */
    async query(payload) {
        if (!mCloudDBZone) {
            logger.info("CloudDBClient is null, try re-initialize it")
            console.log("CloudDBClient is null, try re-initialize it")
            return
        }
        const { page = 1, pageSize = 5, lastCreatedAt = '' } = payload
        // 需要考虑 payer 和 payee 两种情况，也就是说 用户即需要展示自己的入帐记录，也需要展示自己的出账
        // 分页查询，参考文档: https://developer.huawei.com/consumer/cn/doc/AppGallery-connect-Guides/agc-clouddb-otheroperations-servernodejs-0000001569466329#section52476494289
        const outcomeResp = await mCloudDBZone.executeQuery(
            clouddb.CloudDBZoneQuery.where('payRecords')
                .equalTo('payer', payload.uid)
                .orderByDesc('createdAt')
                .limit(pageSize, Math.max(0, page - 1) * pageSize)
        )

        const incomeResp = await mCloudDBZone.executeQuery(
            clouddb.CloudDBZoneQuery.where('payRecords')
                .equalTo('payee', payload.uid)
                .orderByDesc('createdAt')
                .limit(pageSize, Math.max(0, page - 1) * pageSize)
        )
        // 将出入帐记录进行组装
        const outcomeData = outcomeResp.getSnapshotObjects()
        const incomeData = incomeResp.getSnapshotObjects()
        const totalData =  incomeData.concat(outcomeData)
        // 由于目前云数据库不支持join，因此只能先查询用户信息，进行补全
        let users = totalData.reduce((list, item) => {
            list.push(item.getFieldValue('payer'))
            list.push(item.getFieldValue('payee'))
            return list
        }, [])
        // 通过Set进行去重
        users = Array.from(new Set(users))
        // 获取所有的用户信息
        let userInfos = await mCloudDBZone.executeQuery(
            clouddb.CloudDBZoneQuery.where('user')
                .in('uid', users)
        )
        userInfos = userInfos.getSnapshotObjects()


        // 进行用户信息填补
        const data = totalData.map(a => {
            const originData = Object.fromEntries(a.getFieldMap())
            const payerInfo = userInfos.find(u => u.getFieldValue('uid') === originData.payer)
            const payeeInfo = userInfos.find(u => u.getFieldValue('uid') === originData.payee)
            return {
                ...originData,
                payerInfo: payerInfo ? Object.fromEntries(payerInfo.getFieldMap()) : {},
                payeeInfo: payeeInfo ? Object.fromEntries(payeeInfo.getFieldMap()) : {},
            }
        })

        return data
    }

    async patch(payload) {
        if (!mCloudDBZone) {
            logger.info("CloudDBClient is null, try re-initialize it");
            console.log("CloudDBClient is null, try re-initialize it")
            return;
        }
        try {
            // 注意使用CloudDBZoneGenericObject进行构建
            const user = clouddb.CloudDBZoneGenericObject.build('user');
            user.addFieldValue('name', payload.name);
            user.addFieldValue('uid', payload.uid);
            user.addFieldValue('email', payload.email);
            user.addFieldValue('avatar', payload.avatar);
            return mCloudDBZone.executeUpsert(user);
        } catch (error) {
            console.log(error)
        }
    }
}

module.exports = CloudDBZoneWrapper;